#include "stdafx.h"
#include "Scene.h"
#include "MeshModel.h"
#include "PrimMeshModel.h"
#include <string>

using namespace std;
void Scene::loadOBJModel(string fileName,bool centralize, bool shrink, vec3 mp)
{
	MeshModel *model = new MeshModel(fileName);
	models.push_back(model);
	if (centralize) model->Centralize();
	if (shrink) model->Shrink();
	model->TranslateModel(mp.x,mp.y,mp.z);
	activeModel++; // the last loaded model is the one I set active...
}

void Scene::redraw() {
	m_renderer->ClearOutBuffer();
	this->draw();
}

void Scene::draw()
{
	m_renderer->SetProjection(this->cameras[activeCamera]->getProjectionTransform());

	for (int i=0; i< models.size(); i++) {
		MeshModel *m = (MeshModel*)models[i]; // I  really hate doing it...
		if (i==activeModel) {
			m_renderer->TakeActivePen();
		}
		else {
			m_renderer->TakeRegularPen();
		}
		m->SetRenderer(m_renderer);
		m->SetCamera(this->cameras[activeCamera]);
		m->draw();
	}

	if (this->m_drawCameras) {
		for (int i=0; i< cameras.size(); i++) {
			vec4 c_place = cameras[i]->getPlace();
			m_renderer->SetObjectMatrices(cameras[activeCamera]->getCameraTransform(),NULL);
			m_renderer->DrawPlusAtVertex(c_place);
		}
	}
	m_renderer->SwapBuffers();
}

void Scene::drawDemo()
{
	m_renderer->SetCameraTransform(lookAt(vec3(0,0,0),vec3(1,0,0),vec3(0,1,0)));
	m_renderer->SetDemoBuffer();
	m_renderer->SwapBuffers();
}

void Camera::LookAt(const vec3& eye, const vec3& at, const vec3& up) {
	vec3 n,u,v;
	this->placeInWorld = vec4(eye.x,eye.y,eye.z,1);
	n = normalize(eye-at); // OK, we set up the vpn... good...
	u=normalize(cross(up,n));
	v = normalize(cross(n,u));
	vec4 n_ext = vec4(n.x,n.y,n.z,0);
	vec4 u_ext = vec4(u.x,u.y,u.z,0);
	vec4 v_ext = vec4(v.x,v.y,v.z,0);
	vec4 t = vec4(0,0,0,1);
	// this is the already transposed rotation matrix...
	mat4 rotator = mat4(u_ext,v_ext,n_ext,t);
	mat4 translator = Translate(-eye);
	this->cTransform = rotator*translator;
	// now we have to calculate the Transpose(M) matrix...
	this->toWorldTransformer = transpose(mat4(u_ext,v_ext,n_ext,vec4(eye.x,eye.y,eye.z,1)));

	m_eye = eye;
	m_at = at;
	m_up = up;
}

void Camera::Ortho( const GLfloat left, const GLfloat right,
		const GLfloat bottom, const GLfloat top,
		const GLfloat zNear, const GLfloat zFar ) {vec4((m_left+m_right)/2,(m_top+m_bottom)/2,(m_zNear + m_zFar)/2,1);

	m_is_ortho = true;
	m_left = left;
	m_right = right;
	m_top = top;
	m_bottom = bottom;
	m_zNear = zNear;
	m_zFar = zFar;
	projection = Scale(2/(right-left),2/(top-bottom),2/(zNear-zFar))*Translate(-vec3((left+right)/2,(top+bottom)/2,(zNear+zFar)/2));
}

void Camera::UpdateProjection(const GLfloat & ratio) {
	GLfloat newHeight = (m_right - m_left) / ratio;
	GLfloat center = (m_top + m_bottom)/2;
	if (this->isOrtho()) {
		Ortho(m_left,m_right,center-newHeight/2,center+newHeight/2,m_zNear,m_zFar);
	} else {
		Frustum(m_left,m_right,center-newHeight/2,center+newHeight/2,m_zNear,m_zFar);
	}
}

void Camera::Frustum( 
		const float left, const float right,
		const float bottom, const float top,
		const float zNear, const float zFar ) {
	m_is_ortho = false;
	m_left = left;
	m_right = right;
	m_top = top;
	m_bottom = bottom;
	m_zNear = zNear;
	m_zFar = zFar;
	this->projection = mat4(vec4(2*zNear/(right-left),0,(right+left)/(right-left),0),
								vec4(0,2*zNear/(top-bottom),(top+bottom)/(top-bottom),0),
								vec4(0,0,-(zFar+zNear)/(zFar-zNear),(-2*zFar*zNear)/(zFar-zNear)),
								vec4(0,0,-1,0));
}



void Camera::Perspective( const float fovy, const float aspect, const float zNear, const float zFar) {
	GLfloat bottom = zNear*(tan(-fovy/2)); 
	GLfloat top = zNear * (tan(fovy/2));
	GLfloat width = aspect*(top-bottom);
	GLfloat left = -width/2;
	GLfloat right = width/2;
	Frustum(left,right,bottom,top,zNear,zFar);
}

void Camera::Rotate(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	vec4 a = RotateX(x) * RotateY(y) * RotateZ(z) * vec4(0,0,-1,1);
	a = toWorldTransformer * a;
	vec4 u =toWorldTransformer * RotateX(x) * RotateY(y) * RotateZ(z) * vec4(0,1,0,1);
	// us is the head of the up vector in world coordinates...
	vec3 u3d = vec3(u.x,u.y,u.z) - m_eye;
	LookAt(m_eye, vec3(a.x,a.y,a.z), u3d);
}

void Camera::Move(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	// no need to change the up vector...
	// only change the eye and the at points...
	vec4 e = toWorldTransformer * Translate(x,y,z) * vec4(0,0,0,1);
	vec4 a = toWorldTransformer * Translate(x,y,z) * vec4(0,0,-1,1);
	LookAt(vec3(e.x,e.y,e.z),vec3(a.x,a.y,a.z),m_up);
}

Scene::Scene(Renderer * r):m_renderer(r),m_drawCameras(0) {
	//set the default camera...
	this->cameras = vector<Camera*>();
	vec3 eye(0,0,0);
	vec3 at(0,0,-1);
	vec3 up(0,1,0);
	Camera * c = new Camera();
	c->LookAt(eye,at,up);
	c->Ortho(-1,1,-1,1,1,-1);
	cameras.push_back(c);
	this->activeCamera=0;
	this->activeModel = -1; // no model loaded still...
	flagDrawNormals = false;
}

void Scene::TranslateActiveModel(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	if (activeModel==-1) return;
	MeshModel * model = (MeshModel*) this->models[activeModel];
	model->TranslateModel(x,y,z);
}

void Scene::NextModel() {
	this->activeModel = (this->activeModel + 1) % this->models.size();
}

void Scene::PreviousModel() {
	activeModel = (activeModel + models.size() - 1) % models.size();
}

void Scene::SpinModel(const GLfloat & x_angle, const GLfloat & y_angle, const GLfloat & z_angle) {
	if (activeModel==-1) return;
	MeshModel * model = (MeshModel*) this->models[activeModel];
	model->SpinModel(x_angle,y_angle,z_angle);
}

void Scene::RotateModelAboutX(const GLfloat & angle) {
	if (activeModel==-1) return;
	MeshModel * model = (MeshModel*) this->models[activeModel];
	model->RotateAboutX(angle);
}

void Scene::RotateModelAboutY(const GLfloat & angle) {
	if (activeModel==-1) return;
	MeshModel * model = (MeshModel*) this->models[activeModel];
	model->RoateteAboutY(angle);
}

void Scene::RotateModelAboutZ(const GLfloat & angle) {
	if (activeModel==-1) return;
	MeshModel * model = (MeshModel*) this->models[activeModel];
	model->RotateAboutZ(angle);
}

void Camera::getProjectionDimensions(GLfloat & width, GLfloat & height, GLfloat & depth) {
	width = m_right - m_left;
	height = m_top - m_bottom;
	depth = m_zNear - m_zFar;
}

void Scene::FocusOnActiveModel() {
	if (activeModel == -1) return;
	Camera * curr = cameras[activeCamera];
	MeshModel * m = (MeshModel*)models[activeModel];

	vec4 origin = m->getOrigin();
	vec4 origInWorld = m->getOriginInWorld();
	
	GLfloat width, height, depth;
	vec3 origIn3D = vec3(origInWorld.x,origInWorld.y,origInWorld.z);
	GLfloat distance = length(origIn3D);

	curr->getProjectionDimensions(width,height,depth);
	// now we want a symmetric projection
	if (curr->isOrtho()) {
		// update the orthographic projection to look right at the center of the cube encompassing the object...
		curr->Ortho(-width/2, width/2,-height/2,height/2,-distance+depth/2,-distance-depth/2);
	}

	vec3 eye = curr->getEye();
	vec3 at = curr->getAt();
	vec3 up = curr->getUp();

	if ((origin.x==0) && (origin.z==0)) {
		if (origin.y==0) {
			return;
		}
		vec4 new_up;
		if (origin.y<0) {
			//we have to look down...
			new_up = curr->getCorrector() * vec4(0,0,-1,1);
		} else {
			new_up = curr->getCorrector() * vec4(0,0,1,1);
		}
		curr->LookAt(eye,origIn3D,VEC4TOVEC3(new_up));
	} 
	else {
		curr->LookAt(eye,origIn3D,up);
	}
}

void Scene::ToggleFaceNormals(){
	m_renderer->toggleFaceNormals();
}

void Scene::ToggleVertexNormals(){
	m_renderer->toggleVertexNormals();
}

void Scene::RotateCamera(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	Camera * c = cameras[activeCamera];
	c->Rotate(x,y,z);
}

void Scene::TranslateCamera(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	Camera * c = cameras[activeCamera];
	c->Move(x,y,z);
}

void Scene::ScaleActiveModel(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	MeshModel * m = (MeshModel*) models[activeModel];
	m->ScaleMe(x,y,z);
}

void Scene::SymScaleActiveModel(const GLfloat & r) {
	MeshModel * m = (MeshModel*) models[activeModel];
	m->ScaleMe(r,r,r);
}

void Scene::UpdateProjectionsForRatio(GLfloat ratio) {
	for (int i=0; i<cameras.size(); i++ ) {
		cameras[i]->UpdateProjection(ratio);
	}
}
void Scene::toggleBoundingBox(){
	m_renderer->toggleBoundingBox();
}

void Scene::addArk(){
	PrimMeshModel *ark = new Ark(m_renderer);
	ark->build(vec3(-0.5,-0.5,-0.5),vec3(0.5,0.5,0.5));
	models.push_back(ark);
	activeModel++; // the last loaded model is the one I set active...
}

void Scene::removeActiveModel(){
	if(models.size() == 0){
		return;
	}
	vector<Model*>::iterator iter = models.begin();
	printf("\nactive model = %d\n\n\n\n\n\n\n\n", activeModel);
	for(int i=0;i<activeModel;i++){
		iter++;
	}

	models.erase(iter);
	activeModel = activeModel++;
	if(models.size() != 0){
		activeModel = activeModel % models.size();
	}
	else{
		activeModel = -1;
	}
}

void Scene::removeAllModels(){
	while(models.size() != 0){
		removeActiveModel();


	}
}
Scene::~Scene() {
	for (int i=0; i< this->cameras.size(); i++) {
		Camera * c = cameras[i];
		delete c;
	}
	for (int i=0; i<this->models.size(); i++) {
		Model * m = this->models[i];
		delete m;
	}
}

void Camera::Zoom(GLfloat rate) {
	vec3 projPlaneCenter = vec3((m_left + m_right)/2, (m_top + m_bottom)/2,(m_zNear + m_zFar)/2);
	GLfloat height  = m_top - m_bottom;
	GLfloat width = m_right-m_left;
	height = height / rate;
	width = width / rate;
	// when we perform a zoom, we actually change the projection...
	if (this->isOrtho()) {
		GLfloat depth;
		depth = m_zNear - m_zFar;
		depth = depth/rate;
		// now what we want to do is minimize the cube size...
		Ortho(projPlaneCenter.x - width/2, projPlaneCenter.x + width/2,
			projPlaneCenter.y - height/2, projPlaneCenter.y + height/2,
			projPlaneCenter.z + depth/2,projPlaneCenter.z - depth/2);
	}
	else {
		Frustum(projPlaneCenter.x - width/2, projPlaneCenter.x + width/2,
			projPlaneCenter.y - height/2, projPlaneCenter.y + height/2,
			m_zNear, m_zFar);
	}
}

void Scene::MoveZFar(GLfloat len) {
	// now what we have to do is change the projection...
	Camera * c = cameras[activeCamera];
	//GLfloat left, right, top, bottom, zNear, zFar;
	c->MoveZFar(len);
}

void Scene::MoveZNear(GLfloat len) {
	Camera * c = cameras[activeCamera];
	c->MoveZNear(len);
}

void Camera::MoveZNear(GLfloat len) {
	// the semantcis of zNear and zFar are different fot Orthographic and Perspective projections...
	GLfloat newZNear = m_zNear + len;
	if (isOrtho()) {
		if (newZNear<=m_zFar) return;
		Ortho(m_left,m_right,m_bottom,m_top,newZNear,m_zFar);
	}
	else {
		if (newZNear <=0) return;
		if (newZNear>=m_zFar) return;
		Frustum(m_left,m_right,m_bottom,m_top,newZNear,m_zFar);
	}
}

void Camera::MoveZFar(GLfloat len) {
	// symmetric to MoveZNear...
	GLfloat newZFar = m_zFar + len;
	if (isOrtho()) {
		if (newZFar>=m_zNear) return;
		Ortho(m_left,m_right,m_bottom,m_top,m_zNear,newZFar);
	}
	else {
		if (newZFar <= m_zNear) return;
		Frustum(m_left,m_right,m_bottom,m_top,m_zNear,newZFar);
	}
}

vec3 Camera::SuggestMountingPoint() const {
	vec4 p;
	if (isOrtho()) {
		p = vec4((m_left+m_right)/2,(m_top+m_bottom)/2,(m_zNear + m_zFar)/2,1);
	}
	else {
		p = vec4((m_left+m_right)/2,(m_top+m_bottom)/2,-(m_zNear + m_zFar)/2,1);
	}
	p =  toWorldTransformer * p;
	return vec3(p.x,p.y,p.z);
}

vec3 Scene::SuggestMountingPoint() const {
	if (cameras.size()==0) return vec3(0,0,0);
	return cameras[activeCamera]->SuggestMountingPoint();
}